/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/* 
 * Copyright (c) 2009 City University of Hong Kong
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License m_version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author: Wilson Thong (wilsonwk@ee.cityu.edu.hk) 
 */
#include "path-detail-calculator.h"
#include "ns3/log.h"
#include "ns3/assert.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/point-to-point-channel.h"
#include <sstream>
#include <iostream>

NS_LOG_COMPONENT_DEFINE ("PathDetailCalculator");

namespace ns3 {
namespace eecityu {

using namespace std;

PathDetailCalculator::PathDetailCalculator (void) : m_hopDistance (0), m_pathCost (0)
{
}

void
PathDetailCalculator::Calculate (uint32_t src, uint32_t dst)
{
  NS_LOG_FUNCTION (src << dst);
  return Calculate (NodeList::GetNode (src), NodeList::GetNode (dst));
}

void 
PathDetailCalculator::Calculate (Ptr<Node> src, Ptr<Node> dst)
{
  NS_LOG_FUNCTION (src->GetId () << dst->GetId ());

  // reset the path attributes
  m_hopDistance = 0;
  m_pathCost = 0;

  while (src != dst)
  {
    // create a dummy packet and try routing it from the source node 
    // to destination node and the hops distance
    Ptr<Packet> p = Create<Packet> (0);
    Ptr<Ipv4> dstIpv4 = dst->GetObject<Ipv4> ();
    NS_ASSERT_MSG (dstIpv4, "Ipv4 object not found from the destination node!");
    // obtain any one of the IP address from the destination node for measuring 
    // the path destination
    Ipv4Address dest = dstIpv4->GetAddress (1, 0).GetLocal ();
    Ipv4Header header;
    header.SetDestination (dest);
    Socket::SocketErrno errno_;
    Ptr<Ipv4Route> route;
    Ptr<NetDevice> oif = 0; //specify non-zero if bound to a source address
    Ptr<Ipv4> srcIpv4 = src->GetObject<Ipv4> ();   
    // Get the route for reaching the next hop toward the destination
    route = srcIpv4->GetRoutingProtocol ()->RouteOutput (p, header, oif, errno_);   
    NS_LOG_LOGIC ("Route to next hop = " << *route);
    Ptr<PointToPointNetDevice> localDev = DynamicCast<PointToPointNetDevice> (route->GetOutputDevice ());
    // get the net device on the next hop
    Ptr<PointToPointNetDevice> remoteDev = localDev;
    // go to the remote net device
    uint32_t devIndex = 0;
    Ptr<PointToPointChannel> channel = DynamicCast<PointToPointChannel> (localDev->GetChannel ());
    while (remoteDev == localDev)
    {
      NS_ASSERT_MSG (devIndex < channel->GetNDevices (), "All net devices on the channel is checked but the expected next hop is not found!");
      remoteDev = DynamicCast<PointToPointNetDevice> (channel->GetDevice (devIndex++));    
    }
    // Ipv4 object of the next-hop
    Ptr<Ipv4> nxtIpv4 = remoteDev->GetNode ()->GetObject<Ipv4> ();
    // Get the local and remote interfaces' IP-address
    uint32_t localOif  = srcIpv4->GetInterfaceForDevice (localDev);
    NS_LOG_LOGIC ("Local if index=" << localOif);
    uint32_t remoteIif = nxtIpv4->GetInterfaceForDevice (remoteDev);
    NS_LOG_LOGIC ("Remote if index=" << remoteIif);
    Ipv4Address localAddr =  srcIpv4->GetAddress (localOif,  0).GetLocal ();
    Ipv4Address remoteAddr = nxtIpv4->GetAddress (remoteIif, 0).GetLocal ();
    NS_LOG_LOGIC ("local net-device=" << localAddr << ". Remote net-device=" << remoteAddr);
    // check if the remote IP address is the same as the gateway IP address specified in the route entry
    Ipv4Address gatewayAddr = route->GetGateway ();
    NS_ASSERT_MSG (remoteAddr.IsEqual (gatewayAddr), "Actual next hop IP address does not match with the route's next-hop IP address");
    // get link metric
    int32_t linkMetric = srcIpv4->GetMetric (localOif);
    NS_LOG_LOGIC ("Link metric=" << linkMetric);
    // jump to the next hop
    src = remoteDev->GetNode ();
    NS_LOG_LOGIC ("Next hop ID = " << src->GetId ());

    // calculate the path's attributes
    ostringstream logBefore;
    logBefore << "Before update: hop distance=" << m_hopDistance
      << ", path cost=" << m_pathCost;
    NS_LOG_LOGIC (logBefore.str ());

    m_hopDistance++;
    m_pathCost += linkMetric;

    ostringstream logAfter;
    logAfter << "After update: hop distance=" << m_hopDistance
      << ", path cost=" << m_pathCost;
    NS_LOG_LOGIC (logAfter.str ());
  }
  NS_LOG_LOGIC ("Destination reached");
}

uint32_t 
PathDetailCalculator::GetCalculatedHopDistance () const
{
  NS_LOG_FUNCTION_NOARGS ();

  return m_hopDistance;
}

int32_t 
PathDetailCalculator::GetCalculatedPathCost () const
{
  NS_LOG_FUNCTION_NOARGS ();

  return m_pathCost;
}

} // namespace eecityu
} // namespace ns3
